home *** CD-ROM | disk | FTP | other *** search
/ Nebula 2 / Nebula Two.iso / SourceCode / MiscKit1.7.1 / MiscKit / Palettes / MiscShell / MiscShell.subproj / MiscShell.m < prev    next >
Encoding:
Text File  |  1995-11-01  |  31.2 KB  |  1,231 lines

  1. // Copyright (C) 1995 Steve Hayman
  2. // Use is governed by the MiscKit license
  3.  
  4. #import <misckit/MiscShell.h>
  5.  
  6. /*
  7.  * $Header: /SAHayman/LocalDeveloper/Source/MiscKit/Palettes/MiscShell/MiscShell.subproj/RCS/MiscShell.m,v 1.2 94/08/04 17:39:39 shayman Exp Locker: shayman $
  8.  * $Log:    MiscShell.m,v $
  9.  * Revision 1.2  94/08/04  17:39:39  shayman
  10.  * Fiddling with string values
  11.  * 
  12.  */
  13.  
  14. /*
  15.  * Note:
  16.  * In several spots we do things like
  17.  *   if( [aVar isKindOfClassNamed:"DBTableView"] )
  18.  * instead of
  19.  *   if ( [avar isKindOf:[DBTableView class]] )
  20.  *
  21.  * This is because you can do the first test without having to link
  22.  * DBKit into your program.  Doing the second requires linking DBKit,
  23.  * including the DBKit header files in this program, etc etc, which
  24.  * we'd rather avoid if we can.  Same for NXTableView.  Since I want this
  25.  * object to be usable with either, this seems like the best way to
  26.  * avoid various linking problems.
  27.  *
  28.  * I have done the same thing with other AppKit classes, e.g.
  29.  *  if ( [aVar isKindOfClassNamed:"Matrix"] )
  30.  * for consistency.
  31.  */
  32.  
  33. // To avoid warnings, the following "category" defines some methods:
  34. // Added 1/17/95, DAY
  35. @interface Object(MiscShell_Warning_Suppressor)
  36. - setDataSource:aSource;
  37. - (unsigned int)columnCount;
  38. - columnAt:(unsigned int)aPosition;
  39. - identifier;
  40. - setIdentifier:anIdentifier;
  41. - reloadData:sender;
  42. @end
  43.  
  44.  
  45.  
  46.  
  47. #define MISC_SHELL_VERSION 3
  48.  
  49. @interface MiscShell(PrivateMethods)
  50. - setScriptFromCString:(const char *)str;
  51. - sendText:(const char *)buffer andNewline:(BOOL)newline to:text;
  52. - setupEnvironment:process forSender:sender;
  53. - addEnvVar:(const char *)varName value:(const char *)val to:env;
  54. - handleCompleteLine;
  55. - startOutput;
  56. - finishOutput;
  57. - resort:(int)keyField;
  58.  
  59. - field:(int)n ofString:s;
  60. - splitupCurrentLine;
  61. - (BOOL)useCustomDelimiters;
  62. - (int) fieldsInString:(MiscString *)str;
  63.  
  64. @end
  65.  
  66. @implementation MiscShell(PrivateMethods)
  67.  
  68. - (BOOL)useCustomDelimiters
  69. {
  70.     return [[self customDelimiters] length] > 0;
  71. }
  72. /*
  73.  * Add a bunch of environment variables to a process.
  74.  */
  75. - setupEnvironment:proc forSender:sender
  76. {
  77.     NXBundle *mainBundle = [NXBundle mainBundle];
  78.     id env = [proc environment];    // a MiscStringArray
  79.  
  80.     int i;
  81.     char varName[3];
  82.     id v;
  83.     
  84.     /*
  85.      * Go through each of our v1...v9 instance variables in turn by
  86.      * making use of this clever object_getInstanceVariable runtime function.
  87.      */
  88.     for ( i = 1; i <= 9; i++ ) {
  89.     sprintf(varName, "v%d", i);
  90.     // See if we have an instance variable by that name ..
  91.     if ( object_getInstanceVariable(self, varName, (void *)&v) ) {
  92.     
  93.         // Now see if that obj responds to stringValue:, and if so,
  94.         // add an environment var representing its current stringValue.
  95.         
  96.         // Special case: if the object is a browser, we write
  97.         // out the value of all its selected cells, separated
  98.         // by tabs.  Rather than just the stringValue.
  99.         
  100.         if ( [v isKindOfClassNamed:"NXBrowser"] ) {
  101.         List *selections = [[List alloc] init];
  102.         MiscString *str = [[MiscString alloc] init];
  103.         int i;
  104.         id aCell;
  105.         
  106.         [v getSelectedCells:selections];
  107.         
  108.         i = 0;
  109.         while ( aCell = [selections objectAt:i++] ) {
  110.             [str catFromFormat:"%s", [aCell stringValue]];
  111.             
  112.             if ( i < [selections count] )
  113.             [str catFromFormat:"\t"];
  114.         }
  115.         
  116.         [self addEnvVar:varName value:[str stringValue] to:env];
  117.         [selections free];
  118.         [str free];
  119.             
  120.         } else if ( [v respondsTo:@selector(stringValue)] )
  121.         [self addEnvVar:varName value:[v stringValue] to:env];
  122.     }
  123.     }
  124.  
  125.     
  126.     // Add a var called "sender" containing the sender's string value
  127.     // (if known.)
  128.     
  129.     // TODO - should we be using [sender selectedCell] here if it's
  130.     // a Matrix sending us this message?
  131.     
  132.     
  133.     if ( [sender respondsTo:@selector(stringValue)] ) 
  134.     [self addEnvVar:"sender" value:(const char *)[sender stringValue] 
  135.         to:env];
  136.  
  137.     // What the hell, add a var called "senderTag" too.
  138.     {
  139.     char tag[10];
  140.     if ( [sender respondsTo:@selector(tag)] ) {
  141.         sprintf(tag, "%d", [sender tag]);
  142.         [self addEnvVar:"senderTag" value:tag to:env];
  143.     }
  144.     }
  145.     
  146.     // TODO
  147.     // Maybe if it's a Form sending us the message, we add a bunch
  148.     // of variables for each entry in the Form.
  149.     
  150.     // Add a var called "mainBundle" giving our main application
  151.     // directory.
  152.     
  153.     [self addEnvVar:"mainBundle" value:[mainBundle directory]
  154.     to:env];
  155.     
  156.     
  157.  
  158.     return self;
  159. }
  160. // Add an environment variable to a MiscStringArray, which belongs to
  161. // the subprocess
  162.  
  163. - addEnvVar:(const char *)varName value:(const char *)value to:env
  164. {
  165.     MiscString *newStr = [[MiscString alloc] init];
  166.     if ( ! value )
  167.     return nil;
  168.     [newStr catFromFormat:"%s=%s", varName, value];
  169.     [env addString:[newStr stringValue]];
  170.     [newStr free];
  171.     return self;
  172. }
  173.  
  174. // Internal method, set our script from a (char *)
  175.  
  176. - setScriptFromCString:(const char *)str
  177. {
  178.     if ( script )
  179.     [script free];
  180.     script = [MiscString newWithString:str];
  181.     return self;
  182. }
  183. // Internal method that passes some text along to either a
  184. // scrolling text object (appended), or something responding to setStringValue.
  185. // it's appended to the end.  
  186. // The nl variable controls whether we send a newline or not.
  187. // If true, and if the destination object is a Text object, we append
  188. // a newline.
  189.  
  190. - sendText:(const char *)buffer andNewline:(BOOL) nl to:anObject
  191. {
  192.     int len;
  193.     
  194.     if ( !anObject )
  195.     return nil;
  196.     
  197.     if ( [anObject respondsTo:@selector(setStringValue:)] ) {
  198.     return [anObject setStringValue:buffer];
  199.     }
  200.     
  201.     // Otherwise it should be a text object.
  202.     if ( ! [anObject isKindOfClassNamed:"Text"] ) {
  203.     return nil;
  204.     }
  205.     len = [anObject textLength];
  206.     [anObject setSel:len:len];
  207.     [anObject replaceSel:buffer];
  208.     
  209.     if ( nl ) {
  210.     len = [anObject textLength];
  211.     [anObject setSel:len:len];
  212.     [anObject replaceSel:"\n"];
  213.     }
  214.  
  215.     [anObject scrollSelToVisible];
  216.     
  217.     return self;
  218. }
  219.  
  220. /*
  221.  * just read a line that looks like
  222.  * ALERT;Are you sure?;Yes;No
  223.  * Do an NXRunAlertPanel and write back the result as an integer.
  224.  */
  225. - doAlert
  226. {
  227.     MiscString *message, *b1, *b2, *b3;
  228.     int r;
  229.     char rbuf[10];
  230.  
  231.     message = [currentLine extractPart:1 useAsDelimiter: ';'];
  232.     b1 = [currentLine extractPart:2 useAsDelimiter: ';'];
  233.     b2 = [currentLine extractPart:3 useAsDelimiter: ';'];
  234.     b3 = [currentLine extractPart:4 useAsDelimiter: ';'];
  235.     
  236.     r = NXRunAlertPanel([NXApp appName], 
  237.     [message stringValue],
  238.     [b1 stringValue], [b2 stringValue], [b3 stringValue] );
  239.     
  240.     sprintf(rbuf, "%d", r);
  241.     [process send:rbuf withNewline:YES];
  242.     
  243.     [message free];
  244.     [b1 free];
  245.     [b2 free];
  246.     [b3 free];
  247.     
  248.     return self;
  249. }
  250.  
  251. - doOpen
  252. {
  253.     id op = [OpenPanel new];
  254.     if ( [op runModalForDirectory:NULL file:NULL] ) {
  255.     [process send: [op filename] withNewline:YES];
  256.     } else {
  257.     [process send:"\n" withNewline:NO];
  258.     }
  259.     return self;
  260. }
  261. /*
  262.  * Do whatever is appropriate upon reading a complete line from the shell.
  263.  */
  264. - handleCompleteLine
  265. {
  266.  
  267.     /*
  268.      * Is it one of our special strings?
  269.      */
  270.     /*
  271.      * Run special magic commands that write back answers when
  272.      * we see certain strings in the output.
  273.      */
  274.     MiscString *var, *value;
  275.     id varObj;
  276.     // ALERT;Are you sure?;Yes;No
  277.     if ( [currentLine cmp:"ALERT" n:5] == 0 ) {
  278.     return [self doAlert];
  279.     } else if ( [currentLine cmp:"OPEN" n:4] == 0 ) {
  280.     return [self doOpen];
  281.     }
  282.     
  283.     
  284.     if ( [currentLine matchesRegex:"^v[0123456789]=" ] ) {
  285.     var = [currentLine extractPart:0 useAsDelimiter:'='];
  286.     value = [currentLine extractPart:1 useAsDelimiter:'='];
  287.     
  288.     // ooh tricky runtime stuff - get a pointer to the
  289.     // instance var whose name is in "var"
  290.     if ( object_getInstanceVariable(self, [var stringValue], (void *)&varObj) ) {
  291.         if ( [varObj respondsTo:@selector(setStringValue:)] ) {
  292.         [varObj setStringValue:[value stringValue]];
  293.         }
  294.     }
  295.     [var free];
  296.     [value free];
  297.     return self;
  298.     }
  299.     
  300.     
  301.     /*
  302.      * Add the line to our lines array.
  303.      */
  304.     [lines addString: [currentLine stringValue]];
  305.     
  306.     /*
  307.      * Create a MiscStringArray out of it by splitting it into
  308.      * fields, and add it to the linesBrokenIntoFields List.
  309.      */
  310.     
  311.     [self splitupCurrentLine];
  312.     
  313.     /*
  314.      * Send the target/action message
  315.      */
  316.     if ( _target && action && [_target respondsTo:action] ) {
  317.     [_target perform:action with:self];
  318.     }
  319.     
  320.     /*
  321.      * Send the line - and a newline - to standard output.
  322.      */
  323.     [self sendText:[currentLine stringValue] andNewline:YES to:standardOutput];
  324.     
  325.     /*
  326.      * If standard out is a browser, add a row to it.
  327.      */
  328.     if ( [standardOutput isKindOfClassNamed:"NXBrowser"] ) {
  329.     id m = [standardOutput matrixInColumn:0];
  330.     id newCell;
  331.     
  332.     [m renewRows: [lines count] cols:1];
  333.     newCell = [[m cellList] lastObject];
  334.     
  335.     
  336.     [newCell setStringValue:[currentLine stringValue]];
  337.     [newCell setLeaf:YES];
  338.     [newCell setLoaded:YES];
  339.     [m sizeToCells];
  340.     [standardOutput sizeToFit];
  341.     } else if ( [standardOutput isKindOfClassNamed:"DBTableView"]
  342.         ||  [standardOutput isKindOfClassNamed:"NXTableView"]) {
  343.     ;    // Don't need to do anything here, all handled in finishOutput
  344.         // for now.
  345.     } else if ( [standardOutput isKindOfClassNamed:"Matrix"] ) {
  346.     [standardOutput selectCellWithTag:[self intValue]];
  347.     } else if ( [standardOutput isKindOfClassNamed:"MiscShell"] ) {
  348.     /*
  349.      * Ask the other process to read our string value and
  350.      * place it on its stdin.  This is a way that you can
  351.      * pipeline two shell objects.
  352.      */
  353.     [standardOutput takeStdinFrom:self];
  354.     }
  355.     return self;
  356. }
  357. - (int) fieldsInString:(MiscString *)str
  358. {
  359.     
  360.     if ( [self useCustomDelimiters] ) {
  361.     // n delimiters -> n+1 fields
  362.     return [customDelimiters numWords] + 1;
  363.     } else if ( [self delimiter]  )
  364.     /*
  365.      * count delimiters.
  366.      * one delimiter = two fields.
  367.      */
  368.     return [str numOfChar:[self delimiter]] + 1;
  369.     else    
  370.     /*
  371.      * delimiter is 0 - count words delimited by whitespace
  372.      */
  373.         return [str numWords];
  374.  
  375. }    
  376.  
  377.  
  378. - splitupCurrentLine
  379. {
  380.     int i;
  381.     MiscStringArray *a = [[MiscStringArray alloc] init];
  382.     MiscString *curLine = [[lines strings] lastObject];
  383.     
  384.     // Extract each field in turn, add it to the array for this line
  385.     for ( i = 0; i < [self fieldsInString:curLine]; i++ ) {
  386.     [a addString:  [[self field:i ofString:curLine] stringValue]];
  387.     }
  388.     
  389.     [linesBrokenIntoFields addObject:a];
  390.     return self;
  391. }
  392.     
  393. /*
  394.  * This is here so that we can do clever things if our standardOutput
  395.  * is a table view object.  This method is automatically called when
  396.  * the outlet is initialized.
  397.  * We need to tell the table view that we are its data source, and
  398.  * we need to set identifiers for each of its columns.  Although those
  399.  * identifiers are normally objects, it's apparently ok to use
  400.  * other 32-bit quantities such as integers, so we'll
  401.  * just set the identifier of column "i" to be "i".
  402.  *
  403.  * TODO - is it legal to be doing this here?  is the table view set up
  404.  * properly when setStandardOutput is called?
  405.  */
  406. - setStandardOutput:newOutput
  407. {
  408.     int i;
  409.     standardOutput = newOutput;
  410.     if ([standardOutput isKindOfClassNamed:"DBTableView"]
  411.        || [standardOutput isKindOfClassNamed:"NXTableView"] ) {
  412.     
  413.     [standardOutput setDataSource:self];
  414.     /*
  415.      * Become its delegate so we get column moved messages
  416.      */
  417.     [standardOutput setDelegate:self];
  418.     /*
  419.      * Put numeric identifiers on each of its columns.
  420.      */
  421.     for ( i = 0; i < [standardOutput columnCount]; i++ ) 
  422.         [[standardOutput columnAt:i] setIdentifier:(void *)i];
  423.     }
  424.     
  425.     return self;
  426. }
  427.  
  428. /*
  429.  * startOutput does any special initialization of certain kinds of
  430.  * output objects.
  431.  */
  432.  
  433. - startOutput
  434. {
  435.     return self;
  436. }
  437.  
  438. /*
  439.  * finishOutput is called after the subprocess has executed, and can
  440.  * be used to do any sort of output display cleanup.
  441.  */
  442. - finishOutput
  443. {
  444.     if ([standardOutput isKindOfClassNamed:"DBTableView"]
  445.     || [standardOutput isKindOfClassNamed:"NXTableView"]) {
  446.     [standardOutput reloadData:self];
  447.     } else  if ( [standardOutput isKindOfClassNamed:"NXBrowser"] ) {
  448.     [standardOutput display];
  449.     }
  450.     return self;
  451. }
  452.  
  453. /*
  454.  * Here is a shellsort function, from Kernighan & Ritchie, page 116,
  455.  * modified to pass a 3rd bonus parameter to the comparison routine.
  456. /*
  457.  * Resort the output lines based on the value of column n.
  458.  * We do this by creating a ListSortedByFields which duplicates
  459.  * the existing string list then we tell the ListSortedByFields to
  460.  * sort itself.
  461.  *
  462.  * Multiplying by sortWhenColumnsMove does the right thing for
  463.  * ascending vs descending sorts.
  464.  */
  465.  
  466. - (int)fieldComp:(int)n forLines:(int)a :(int)b
  467. {
  468.     id field1 = [self line:a field:n];
  469.     id field2 = [self line:b field:n];
  470.     
  471.         
  472.     int rval;
  473.     // If field1 looks like a number, compare numerically.
  474.     // TODO - do the right thing if it looks like
  475.     // a time value (HH:MM)
  476.         
  477.     if ( [field1 matchesRegex:":[0-9][0-9]$" ] ) {
  478.     // Replace the ":" with a ".", which will
  479.     // make the comparison work just as if it was a float.
  480.     [field1 replace: ":" with: "."];    
  481.     [field2 replace: ":" with: "."];
  482.     }
  483.     
  484.     if ( [field1 matchesRegex:"^[0-9]"] ) {
  485.         double n1, n2;
  486.  
  487.     n1 = atof([field1 stringValue]);
  488.     n2 = atof( [field2 stringValue] );
  489.     
  490.     // Check for a K or M suffix, multiply appropriately.
  491.     // This is so I can sort the output of "ps" nicely.
  492.     if ( [field1 endcmp:"M"] == 0 )
  493.         n1 *= 1024 * 1024;
  494.     if ( [field1 endcmp:"K"] == 0 )
  495.         n1 *= 1024;
  496.     if ( [field2 endcmp:"M"] == 0)
  497.         n2 *= 1024 * 1024;
  498.     if ( [field2 endcmp:"K"] == 0)
  499.         n2 *= 1024;
  500.         
  501.     
  502.     rval = (n1 < n2) ? -1 : 
  503.             (n1 > n2) ? 1 : 0;
  504.         
  505.     } else {
  506.     /*
  507.      * Do a regular string comparison.
  508.      */
  509.     rval = [field1 compareTo: field2];
  510.     }
  511.     
  512.     return rval * [self sortWhenColumnsMove];
  513. }
  514.  
  515. - resort:(int)fieldNumber
  516. {
  517.     List * strings = [lines strings];
  518.     id a, b;
  519.     int c, d, stride;
  520.     BOOL found;
  521.     int n = [strings count];
  522.     /*
  523.      * ShellSort, from the SortingInAction miniexample
  524.      */
  525. #define STRIDE_FACTOR 3
  526.     stride = 1;
  527.     while ( stride <= n )
  528.     stride = stride * STRIDE_FACTOR + 1;
  529.  
  530.     while ( stride > (STRIDE_FACTOR - 1)) {
  531.     stride = stride / STRIDE_FACTOR;
  532.     for ( c = stride; c < n; c++ ) {
  533.         found = NO;
  534.         d = c - stride;
  535.         while ( (d >= 0) && !found ) {
  536.  
  537.         if ( [self fieldComp:fieldNumber forLines:d:d+stride] > 0 ) {
  538.             // Swap the "lines" array ...
  539.             a = [strings objectAt:d];
  540.             b = [strings objectAt:d+stride];
  541.             [strings replaceObjectAt:d with:b];
  542.             [strings replaceObjectAt:d+stride with:a];
  543.             
  544.             
  545.             //and the linesBrokenIntoFields list
  546.             a = [linesBrokenIntoFields objectAt:d];
  547.             b = [linesBrokenIntoFields objectAt:d+stride];
  548.             [linesBrokenIntoFields replaceObjectAt:d with:b];
  549.             [linesBrokenIntoFields replaceObjectAt:d+stride with:a];
  550.  
  551.             d -= stride;
  552.         } else
  553.             found = YES;
  554.         }
  555.     }
  556.     }
  557.  
  558.     return self;
  559. }
  560.  
  561. /*
  562.  * Utility routine to return a particular field of a particular string,
  563.  * using our delimiter.  If delimiter is 0, we look for blank-separated words;
  564.  * otherwise we look for the particular delimited field.
  565.  */
  566. - field:(int)f ofString:s
  567. {
  568.     id thisField = nil;
  569.     int startPos, endPos;
  570.     MiscString *delim;
  571.     
  572.     if ( [self useCustomDelimiters] ) {
  573.     // customDelimiters is a string like this
  574.     // 0-4 7-9 13-22
  575.     // that defines the boundaries of each field.  So, in this
  576.     // case, if we want field 2, we extract chars 13-22.
  577.     
  578.     delim = [[self customDelimiters] wordNum:f];
  579.     
  580.     // delim is now something like "5-9", extract the
  581.     // starting and ending positions.  If it's "72-", that
  582.     // means "72 to end of line"
  583.     
  584.     if ( [delim stringValue] && (sscanf( [delim stringValue], "%d-%d", &startPos, &endPos) == 2) ) {    
  585.         thisField = [s midFrom:startPos to:endPos];
  586.     } else if ( [delim stringValue ] && (sscanf( [delim stringValue], "%d-", &startPos) == 1) ) {
  587.         thisField = [s midFrom:startPos to: [s length]];
  588.     }
  589.     [delim free];
  590.     } else if ( [self delimiter] )
  591.     thisField = [s extractPart:f 
  592.         useAsDelimiter:[self delimiter]];
  593.     else {
  594.     /*
  595.      * take the column'th word.
  596.      * Current bug in MiscString: wordNum:n for n > numWords returns
  597.      * the last word rather than nil.  So we check that.
  598.      */
  599.     /* if ( f < [s numWords] )
  600.         thisField = [s wordNum:f];
  601.     else
  602.         thisField = nil;
  603.     */ /* I fixed the MiscString bug, so I'm taking this out. -don */
  604.     thisField = [s wordNum:f];
  605.     }
  606.  
  607.     return thisField;
  608. }
  609.  
  610. @end
  611.  
  612.     
  613. @implementation MiscShell(TableViewDelegate)
  614. - (unsigned int) rowCount 
  615. {
  616.     return [self lineCount];
  617. }
  618.  
  619. - (unsigned int) columnCount
  620. {
  621.     return 1;    // ??? todo - what do I put here? does it matter?
  622. }
  623.  
  624. /*
  625.  * This is a table view asking for the value at row aPosition,
  626.  * column identifier.
  627.  */
  628. - getValueFor:identifier at:(unsigned int)aPosition into:aValue
  629. {    
  630.     [aValue setStringValue: 
  631.     [[self line:aPosition field:(int)identifier] stringValue] ];
  632.     return self;
  633. }
  634.  
  635. /*
  636.  * If you're the delegate of a DBTableView, you get these messages
  637.  * when the columns are resized.  Not sure just how I want to deal 
  638.  * with this yet.
  639.  *
  640.  * Current Plan - check the sortWhenColumnsMove variable.
  641.  * If 0, do nothing.
  642.  * If 1, resort ascending
  643.  * If -1, resort descending
  644.  */ 
  645. - tableView:sender movedColumnFrom:(unsigned int) old to:(unsigned int) new
  646. {
  647.     
  648.     /*
  649.      * Resort based on the identifier of the new first column.
  650.      */
  651.     if ( [self sortWhenColumnsMove] ) {
  652.     [self resort: (int)[[sender columnAt:0] identifier]];
  653.     [sender reloadData:self];
  654.     }
  655.     return self;
  656.  
  657. @end
  658.     
  659. @implementation MiscShell
  660.  
  661. + initialize
  662. {
  663.     if (self == [MiscShell class]) {
  664.     /*
  665.     * **** Archiving: READ ME **** After bumping the _VERSION, it is
  666.     * considered common practice to add a comment line indicating the new
  667.     * version number, date, and modifier. Optionally, the reason for the
  668.     * change. There is no need to modify the setVersion message. BJM
  669.     * 5/24/94 
  670.     */
  671.     // version 0: initial.  (sah)
  672.     // version 1: adds customDelimiters var.  (sah, sep 13 1994)
  673.     // version 2: adds linesBrokenIntoFields array (sah, sep 14 1994)
  674.     // version 3: fixes a bug with archiving BOOL vars (sah, jan 4 1995)
  675.     [[MiscShell class] setVersion:MISC_SHELL_VERSION];
  676.     }
  677.     
  678.     return self;
  679. }
  680.  
  681. - init
  682. {
  683.     self = [super init];
  684.     script = [[MiscString alloc] init];
  685.     fullOutput = [[MiscString alloc] init];
  686.     currentLine = [[MiscString alloc] init];
  687.     lines = [[MiscStringArray alloc] init];
  688.     
  689.     [self setDelimiter:0];        // means "parse words"
  690.     [self setRunToCompletion:NO];    // run asynchronously
  691.     [self setSortWhenColumnsMove:NO];
  692.     
  693.     linesBrokenIntoFields = [[List alloc] init];
  694.     return self;
  695. }
  696.  
  697. /*
  698.  * Initialize, and run a non-interactive command and wait for it to terminate.
  699.  */
  700. - initWithCommand:(const char *)cmd
  701. {
  702.     [self init];
  703.     [self setRunToCompletion:YES];
  704.     [self setScriptFromCString:cmd];
  705.     [self executeScript:self];
  706.     
  707.     return self;
  708. }
  709.     
  710.  
  711. - free
  712. {
  713.     if ( script )
  714.     script = [script free];
  715.     if ( process ) {
  716.     [process terminate:self];
  717.     process = [process free];
  718.     }
  719.     if ( fullOutput )
  720.     fullOutput = [fullOutput free];
  721.     [currentLine free];    
  722.     return [super free];
  723. }
  724.  
  725. // Archiving methods
  726.  
  727. - read:(NXTypedStream *)stream
  728. {
  729.     int version;
  730.     Class myClass = [self class];
  731.     int int1, int2;    // compensate for old read/write bugs
  732.     
  733.     [super read:stream];
  734.     version = NXTypedStreamClassVersion(stream, "MiscShell");
  735.     
  736.     switch (version) {
  737.     case MISC_SHELL_VERSION: {
  738.         /*
  739.          * Version 3 correctly reads/writes BOOL vars as "c", not "i"
  740.          */
  741.         standardOutput = NXReadObject(stream);
  742.         standardInput = NXReadObject(stream);
  743.         standardError = NXReadObject(stream);
  744.         v1 = NXReadObject(stream);
  745.         v2 = NXReadObject(stream);
  746.         v3 = NXReadObject(stream);
  747.         v4 = NXReadObject(stream);
  748.         script = NXReadObject( stream );
  749.         _target = NXReadObject( stream );
  750.         NXReadTypes(stream, ":", &action);
  751.         NXReadTypes(stream, "@", &process);
  752.         NXReadTypes(stream, "@", &fullOutput);
  753.         NXReadTypes(stream, "c",  &executionInProgress);
  754.         NXReadTypes(stream, "@",   ¤tLine);
  755.         NXReadTypes(stream, "cc", ¤tLineIsComplete, &runToCompletion);
  756.         NXReadTypes(stream, "@",   &lines);
  757.         NXReadTypes(stream, "c", &delimiter);
  758.         NXReadTypes(stream, "c", &sortWhenColumnsMove);
  759.         NXReadTypes(stream, "@", &customDelimiters);
  760.         NXReadTypes(stream, "@", &linesBrokenIntoFields);    // new for v2
  761.         break;
  762.     }
  763.     case 2: {
  764.         /*
  765.          * Version 2 adds the linesBrokenIntoFields var
  766.          */
  767.         standardOutput = NXReadObject(stream);
  768.         standardInput = NXReadObject(stream);
  769.         standardError = NXReadObject(stream);
  770.         v1 = NXReadObject(stream);
  771.         v2 = NXReadObject(stream);
  772.         v3 = NXReadObject(stream);
  773.         v4 = NXReadObject(stream);
  774.         script = NXReadObject( stream );
  775.         _target = NXReadObject( stream );
  776.         NXReadTypes(stream, ":", &action);
  777.         NXReadTypes(stream, "@", &process);
  778.         NXReadTypes(stream, "@", &fullOutput);
  779.         // Written as int in versions <= 2, but really a char
  780.         NXReadTypes(stream, "i",  &int1); executionInProgress = int1;
  781.         NXReadTypes(stream, "@",   ¤tLine);
  782.         // Written as int in versions <= 2, but really a char
  783.         NXReadTypes(stream, "ii", &int1, &int2);
  784.                 currentLineIsComplete = int1;
  785.                 runToCompletion = int2;
  786.         NXReadTypes(stream, "@",   &lines);
  787.         NXReadTypes(stream, "c", &delimiter);
  788.         // Written as int in versions <= 2, but really a char
  789.         NXReadTypes(stream, "i",  &int1); sortWhenColumnsMove = int1;
  790.         NXReadTypes(stream, "@", &customDelimiters);
  791.         NXReadTypes(stream, "@", &linesBrokenIntoFields);    // new for v2
  792.         break;
  793.     }
  794.     case 1: {
  795.         /*
  796.          * Version 1 adds a customDelimiters instance var
  797.          */
  798.         standardOutput = NXReadObject(stream);
  799.         standardInput = NXReadObject(stream);
  800.         standardError = NXReadObject(stream);
  801.         v1 = NXReadObject(stream);
  802.         v2 = NXReadObject(stream);
  803.         v3 = NXReadObject(stream);
  804.         v4 = NXReadObject(stream);
  805.         script = NXReadObject( stream );
  806.         _target = NXReadObject( stream );
  807.         NXReadTypes(stream, ":", &action);
  808.         NXReadTypes(stream, "@", &process);
  809.         NXReadTypes(stream, "@", &fullOutput);
  810.         // Written as int in versions <= 2, but really a char
  811.         NXReadTypes(stream, "i",  &int1); executionInProgress = int1;
  812.         NXReadTypes(stream, "@",   ¤tLine);
  813.         // Written as int in versions <= 2, but really a char
  814.         NXReadTypes(stream, "ii", &int1, &int2);
  815.                 currentLineIsComplete = int1;
  816.                 runToCompletion = int2;
  817.         NXReadTypes(stream, "@",   &lines);
  818.         NXReadTypes(stream, "c", &delimiter);
  819.         // Written as int in versions <= 2, but really a char
  820.         NXReadTypes(stream, "i",  &int1); sortWhenColumnsMove = int1;
  821.         NXReadTypes(stream, "@", &customDelimiters);
  822.         linesBrokenIntoFields = [[List alloc] init];    // new - need one
  823.         break;
  824.     }
  825.     case 0: {
  826.         standardOutput = NXReadObject(stream);
  827.         standardInput = NXReadObject(stream);
  828.         standardError = NXReadObject(stream);
  829.         v1 = NXReadObject(stream);
  830.         v2 = NXReadObject(stream);
  831.         v3 = NXReadObject(stream);
  832.         v4 = NXReadObject(stream);
  833.         script = NXReadObject( stream );
  834.         _target = NXReadObject( stream );
  835.         NXReadTypes(stream, ":", &action);
  836.         NXReadTypes(stream, "@", &process);
  837.         NXReadTypes(stream, "@", &fullOutput);
  838.         // Written as int in versions <= 2, but really a char
  839.         NXReadTypes(stream, "i",  &int1); executionInProgress = int1;
  840.         NXReadTypes(stream, "@",   ¤tLine);
  841.         // Written as int in versions <= 2, but really a char
  842.         NXReadTypes(stream, "ii", &int1, &int2);
  843.                 currentLineIsComplete = int1;
  844.                 runToCompletion = int2;
  845.         NXReadTypes(stream, "@",   &lines);
  846.         NXReadTypes(stream, "c", &delimiter);
  847.         // Written as int in versions <= 2, but really a char
  848.         NXReadTypes(stream, "i",  &int1); sortWhenColumnsMove = int1;
  849.         linesBrokenIntoFields = [[List alloc] init];    // new - need one
  850.         break;
  851.     }
  852.     default: {
  853.         NXLogError("[%s %s] - unknown version of %s in typed stream",
  854.                 [myClass name], sel_getName(_cmd), 
  855.                 [myClass name]);
  856.         break;
  857.     }
  858.     }
  859.     return self;
  860. }
  861.  
  862. - write:(NXTypedStream *)stream
  863. {
  864.     [super write:stream];
  865.     
  866.     NXWriteObjectReference( stream, standardOutput );
  867.     NXWriteObjectReference( stream, standardInput );
  868.     NXWriteObjectReference( stream, standardError );
  869.     NXWriteObjectReference( stream, v1 );
  870.     NXWriteObjectReference( stream, v2 );
  871.     NXWriteObjectReference( stream, v3 );
  872.     NXWriteObjectReference( stream, v4 );
  873.     NXWriteObject( stream, script );
  874.     NXWriteObjectReference( stream, _target );
  875.     NXWriteTypes(stream, ":",  &action);
  876.  
  877.     NXWriteTypes(stream, "@",  &process);
  878.     NXWriteTypes(stream, "@",  &fullOutput);
  879.     NXWriteTypes(stream, "c",  &executionInProgress);    // BOOL
  880.     NXWriteTypes(stream, "@",  ¤tLine);
  881.     NXWriteTypes(stream, "cc", ¤tLineIsComplete, &runToCompletion); // BOOL
  882.     NXWriteTypes(stream, "@",   &lines);
  883.     NXWriteTypes(stream, "c", &delimiter);
  884.     NXWriteTypes(stream, "c", &sortWhenColumnsMove);
  885.     NXWriteTypes(stream, "@", &customDelimiters);    // added in v1
  886.     NXWriteTypes(stream, "@", &linesBrokenIntoFields);    // added in v2
  887.  
  888.     return self;
  889. }
  890.  
  891. - target { return _target; }
  892. - setTarget:aTarget
  893. {
  894.     _target = aTarget;
  895.     return self;
  896. }
  897. - (SEL)action { return action; }
  898. - setAction:(SEL)anAction
  899. {
  900.     action = anAction;
  901.     return self;
  902. }
  903.  
  904. /*
  905.  * Our "string value" is the "current" line we've received 
  906.  * (which doesn't contain a newline.)
  907.  */
  908.  
  909. - (const char *)stringValue 
  910. {
  911.     return [currentLine stringValue];
  912. }
  913.  
  914. /*
  915.  * Int, double, float values are just derived from our string value.
  916.  * note: no error checking as to whether the string value really is
  917.  * a number.
  918.  */
  919.  
  920. - (double) doubleValue
  921. {
  922.     return ( atof([self stringValue]) );
  923. }
  924.  
  925. - (float) floatValue
  926. {
  927.     return ( (float) atof([self stringValue]) );
  928. }
  929. - (int) intValue
  930. {
  931.     return ( atoi([self stringValue]) );
  932. }
  933.  
  934.  
  935. /*
  936.  * Set and retrieve the actual script.
  937.  * We store it internally as a MiscString
  938.  */
  939.  
  940. - (MiscString *)script
  941. {
  942.     return script;
  943. }
  944.  
  945. // Inspector sends this when the script changes
  946. - setScript:(MiscString *)newScript
  947. {
  948.     return [self setScriptFromCString: [newScript stringValue]];
  949. }
  950. - (MiscString *)customDelimiters
  951. {
  952.     return customDelimiters;
  953. }
  954.  
  955. // Inspector sends this when the script changes
  956. - setCustomDelimiters:(MiscString *)d
  957. {
  958.     [customDelimiters free];
  959.     customDelimiters = [d copy];
  960.     return self;
  961. }
  962.  
  963. // Messages from Controls
  964.  
  965. - executeScript:sender
  966. {
  967.  
  968.     if ( executionInProgress ) {
  969.     NXBeep();
  970.     return nil;
  971.     }
  972.     
  973.     if ( process ) {
  974.     [process terminate:self];
  975.     [process free];
  976.     }
  977.     
  978.     /*
  979.      * Get rid of old accumulated output.
  980.      */
  981.     
  982.     [fullOutput setStringValue:""];
  983.     [currentLine setStringValue:""];
  984.     currentLineIsComplete = NO;
  985.     [[lines strings] freeObjects];
  986.     
  987.     [linesBrokenIntoFields freeObjects];
  988.     
  989.     /*
  990.      * Here we have special pre-output checks for certain kinds of
  991.      * output objects.
  992.      */
  993.     [self startOutput];
  994.     
  995.     // Create a subprocess to execute the script.  Don't run it just yet.
  996.     
  997.     process = [[MiscSubprocess alloc] init:NULL withDelegate:self];
  998.     
  999.     
  1000.     // Set up subprocess environment here - a bunch of
  1001.     // environment variables that tell the process about
  1002.     // the values of v1, v2, etc.
  1003.     
  1004.     [self setupEnvironment:process forSender:sender];
  1005.     
  1006.     // And finally start the process going.  
  1007.         
  1008.     [process execute:[script stringValue] 
  1009.     withPtys:NO
  1010.     asynchronously: ![self runToCompletion]];
  1011.                    
  1012.     return self;
  1013. }
  1014.  
  1015. /*
  1016.  * These messages might arrive from either a control or a matrix of
  1017.  * controls. 
  1018.  */
  1019.  
  1020. - executeFromStringValue:sender
  1021. {
  1022.     if ( [sender isKindOfClassNamed:"Matrix"] )
  1023.     sender = [sender selectedCell];
  1024.     
  1025.     [self setScriptFromCString:[sender stringValue]];
  1026.     return [self executeScript:sender];
  1027. }
  1028. - executeFromTitle:sender
  1029. {
  1030.     if ( [sender isKindOfClassNamed:"Matrix"] )
  1031.     sender = [sender selectedCell];
  1032.  
  1033.     [self setScriptFromCString:[sender title]];
  1034.     return [self executeScript:sender];
  1035. }
  1036.  
  1037. - executeFromAltTitle:sender
  1038. {
  1039.     if ( [sender isKindOfClassNamed:"Matrix"] )
  1040.     sender = [sender selectedCell];
  1041.  
  1042.     [self setScriptFromCString:[sender altTitle]];
  1043.     return [self executeScript:sender];
  1044. }
  1045.  
  1046. - pause:sender
  1047. {
  1048.     return [process pause:sender];
  1049. }
  1050. - resume:sender
  1051. {
  1052.     return [process resume:sender];
  1053. }
  1054.  
  1055. - terminate:sender
  1056. {
  1057.     return [process terminate:sender];
  1058. }
  1059.  
  1060. /*
  1061.  * Methods that return particular lines, or fields within lines.
  1062.  */
  1063.  
  1064. - (int)lineCount {
  1065.     return [lines count];
  1066. }
  1067.  
  1068. - (int) fieldsInLine:(int)n
  1069. {
  1070.     return [[linesBrokenIntoFields objectAt:n] count];
  1071. }
  1072.  
  1073. - (MiscStringArray *)lines { return lines; }
  1074.  
  1075. /*
  1076.  * Return a copy of the MiscString that holds line number n.
  1077.  * We return a copy for consistency with the line:field: method, so that*
  1078.  * the caller is responsible for freeing both.
  1079.  * Just returning [[lines strings] objectAt:n] would hand back a string
  1080.  * owned by the MiscStringArray object, which the caller shouldn't free.
  1081.  *
  1082.  * Boy it will be nice when libFoundation_s.a is available everywhere
  1083.  * and we can do this more rationally.
  1084.  */
  1085. - (MiscString *)line:(int)n
  1086. {
  1087.     id str = [[lines strings] objectAt:n];
  1088.     if ( str )
  1089.     return [str copy];
  1090.     else
  1091.     return nil;
  1092. }
  1093.  
  1094. /*
  1095.  * Return the MiscString representing field f of line n;
  1096.  * the caller should NOT free it.
  1097.  */
  1098. - (MiscString *)line:(int)n field:(int)f
  1099. {
  1100.     return [[[linesBrokenIntoFields objectAt:n] strings] objectAt:f];
  1101. }
  1102.  
  1103.  
  1104.  
  1105. // MiscSubprocess delegate methods
  1106. -  subprocess:sender output:(const char *)buffer
  1107. {
  1108.     
  1109.     /*
  1110.      * fullOutput records the entire output of the script, so add
  1111.      * the buffer to the end.
  1112.      */
  1113.     [fullOutput cat:buffer];
  1114.  
  1115.     /*
  1116.      * Now.  We have to decide how this new chunk of data, with
  1117.      * possibly embedded newlines, affects the current line.  We want
  1118.      * to fire off a target/action message every time we receive a newline.
  1119.      */
  1120.     
  1121.     
  1122.     while ( *buffer ) {
  1123.     
  1124.     /*
  1125.      * If we have previously accumulated a complete newline, it's
  1126.      * no longer complete.
  1127.      */
  1128.     if( currentLineIsComplete ) {
  1129.         [currentLine setStringValue:""];
  1130.         currentLineIsComplete = NO;
  1131.     }
  1132.  
  1133.     /*
  1134.      * If we are looking at a newline, then the current output
  1135.      * line is complete, so fire the target/action message.
  1136.      */
  1137.     if ( *buffer == '\n' ) {
  1138.         
  1139.         [self handleCompleteLine];
  1140.         currentLineIsComplete = YES;
  1141.         
  1142.        
  1143.     } else {
  1144.         /*
  1145.         * Add this non-newline character
  1146.         */
  1147.         [currentLine addChar:*buffer];
  1148.     }
  1149.     buffer++;
  1150.  
  1151.     }    
  1152.     return self;
  1153. }
  1154.  
  1155. // todo - if standardError and standardOutput are the same, why not
  1156. // just forward this message to subprocess:stdoutOutput, which would merge
  1157. // stderr and stdout handling (and allow target/action for stderr messages)
  1158. -  subprocess:sender stderrOutput:(const char *)buffer
  1159. {
  1160.     if ( standardError )
  1161.     [self sendText:buffer andNewline:NO to:standardError];
  1162.     else
  1163.     fputs( buffer, stderr );    // To the console.
  1164.     return self; // added to remove warning... -- DAY
  1165. }
  1166.  
  1167. - subprocess:sender done:(int)status :(MiscSubprocessEndCode)code
  1168. {
  1169.     executionInProgress = NO;
  1170.     
  1171.     [self finishOutput];
  1172.     return self;
  1173. }
  1174.  
  1175.  
  1176. - (BOOL) runToCompletion { return runToCompletion; }
  1177. - setRunToCompletion:(BOOL)c;
  1178. {
  1179.     runToCompletion = c;
  1180.     return self;
  1181. }
  1182. - (int) sortWhenColumnsMove { return sortWhenColumnsMove; }
  1183. - setSortWhenColumnsMove:(int)i
  1184. {
  1185.     sortWhenColumnsMove = i;
  1186.     return self;
  1187. }
  1188. - (char)delimiter { return delimiter; }
  1189. - setDelimiter:(char)c
  1190. {
  1191.     delimiter = c;
  1192.     return self;
  1193. }
  1194.  
  1195.  
  1196. - setExecArgs:(const char *)a1:(const char *)a2:(const char *)a3
  1197. {
  1198.     ;    // TODO - finish me
  1199.     return self;
  1200. }
  1201.  
  1202. /*
  1203.  * Methods for sending data to the standard input of a process.
  1204.  */
  1205.  
  1206. /*
  1207.  * takeStdinFrom:sender writes [sender stringValue] followed by a newline
  1208.  * to the process.
  1209.  */
  1210. - takeStdinFrom:sender
  1211. {
  1212.     [self writeToStdin:[sender stringValue]];
  1213.     [self writeToStdin:"\n"];
  1214.     return self;
  1215. }
  1216.  
  1217. /*
  1218.  * writeToStdin writes a string verbatim to the standard input of the 
  1219.  * process.
  1220.  */
  1221.  
  1222. - writeToStdin:(const char *)str
  1223. {
  1224.     [process send:str withNewline:NO];
  1225.     return self;
  1226. }
  1227.  
  1228.  
  1229. @end
  1230.